package jmine.tec.web.servlet.cipher.impl;

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.security.InvalidKeyException;
import java.security.KeyPair;
import java.security.PublicKey;

import javax.crypto.Cipher;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import jmine.tec.utils.cripto.cipher.CipherOutputStream;
import jmine.tec.utils.cripto.key.KeyPairHolder;
import jmine.tec.utils.cripto.key.PublicKeyLocator;
import jmine.tec.web.servlet.cipher.SecureCommunicationHandler;
import jmine.tec.web.servlet.cipher.SecureCommunicationProtocol;
import jmine.tec.web.servlet.cipher.challenge.Assertion;
import jmine.tec.web.servlet.cipher.challenge.AssertionFactory;

/**
 * Estado inicial do {@link SecureCommunicationHandler}.
 * 
 * @author takeshi
 */
public class InitialSecureCommunicationHandler extends AbstractSecureCommunicationHandler implements SecureCommunicationHandler {

    public static final int DEFAULT_CHUNK_SIZE = 55;

    private AssertionFactory assertionFactory;

    private transient Cipher cipher;

    private int chunkSize = DEFAULT_CHUNK_SIZE;

    private transient KeyPairHolder keyPairFactory;

    private transient PublicKeyLocator publicKeyLocator;

    /**
     * {@inheritDoc}
     */
    @Override
    public SecureCommunicationHandler nextStage(HttpServletRequest httpRequest, HttpServletResponse httpResponse) throws IOException {
        if (cipher == null) {
            return null;
        }
        String publicKeyPath = this.findKeyPath(httpRequest);
        PublicKey key = this.publicKeyLocator.getPublicKey(publicKeyPath);
        if (key == null) {
            this.getLogger().warn("unable to find specified remote key: " + publicKeyPath);
            httpResponse.setStatus(HttpServletResponse.SC_NOT_FOUND);
            return this;
        }
        try {
            KeyPair keyPair = this.getKeyPairFactory().get();
            Assertion challenge = this.getAssertionFactory().newAssertion();
            ObjectOutputStream oout =
                    new ObjectOutputStream(new CipherOutputStream(httpResponse.getOutputStream(), this.getCipher(), key, this.chunkSize));
            if (this.getLogger().isDebugEnabled()) {
                this.getLogger().debug("sending public key and challenge: " + challenge.getQuestion());
            }
            oout.writeObject(challenge.getQuestion());
            if ("symetric".equals(httpRequest.getParameter("encryption"))) {
                oout.writeObject("DES");
                oout.flush();
                return new SymetricCheckAssertionSecureCommunicationHandler(challenge, key, keyPair.getPrivate(), this.chunkSize, this.cipher);
            }
            oout.flush();
            return new CheckAssertionSecureCommunicationHandler(challenge, key, keyPair.getPrivate(), this.chunkSize, this.cipher);
        } catch (InvalidKeyException e) {
            httpResponse.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
            this.getLogger().warn("cannot use specified key: " + publicKeyPath, e);
            return this;
        }
    }

    /**
     * @return the publicKeyLocator
     */
    public PublicKeyLocator getPublicKeyLocator() {
        return this.publicKeyLocator;
    }

    /**
     * @param publicKeyLocator the publicKeyLocator to set
     */
    public void setPublicKeyLocator(PublicKeyLocator publicKeyLocator) {
        this.publicKeyLocator = publicKeyLocator;
    }

    /**
     * @param httpRequest {@link HttpServletRequest}
     * @return String o path no classpath onde estah a chave
     */
    private String findKeyPath(HttpServletRequest httpRequest) {
        return httpRequest.getParameter(SecureCommunicationProtocol.KEY_PATH_PARAMETER_NAME);
    }

    /**
     * @return the assertionFactory
     */
    public AssertionFactory getAssertionFactory() {
        return this.assertionFactory;
    }

    /**
     * @param assertionFactory the assertionFactory to set
     */
    public void setAssertionFactory(AssertionFactory assertionFactory) {
        this.assertionFactory = assertionFactory;
    }

    /**
     * @param cipher the cipher to set
     */
    public void setCipher(Cipher cipher) {
        this.cipher = cipher;
    }

    /**
     * @return the cipher
     */
    public Cipher getCipher() {
        return this.cipher;
    }

    /**
     * @return the chunkSize
     */
    public int getChunkSize() {
        return this.chunkSize;
    }

    /**
     * @param chunkSize the chunkSize to set
     */
    public void setChunkSize(int chunkSize) {
        this.chunkSize = chunkSize;
    }

    /**
     * @return the keyPairFactory
     */
    public KeyPairHolder getKeyPairFactory() {
        return this.keyPairFactory;
    }

    /**
     * @param keyPairFactory the keyPairFactory to set
     */
    public void setKeyPairFactory(KeyPairHolder keyPairFactory) {
        this.keyPairFactory = keyPairFactory;
    }

}
